package lol.clann.bukkit.pluginboot.util;

import lol.clann.clannboot.annotation.inject.Inject;
import lol.clann.clannboot.annotation.svc.PostInstance;
import lol.clann.clannboot.annotation.svc.Service;
import lol.clann.clannboot.util.clazz.*;
import org.nutz.lang.Lang;

import java.io.*;
import java.util.*;

/**
 * @author pyz
 * @date 2018/8/7 下午9:59
 */
@Service(priority = Integer.MAX_VALUE)
public class NBTCompoundUtils {

    public static final WrapClass classNBTTagCompound = ClassUtils.forName("{nms}.NBTTagCompound");
    public static final WrapClass classNBTCompressedStreamTools = ClassUtils.forName("{nms}.NBTCompressedStreamTools");

    private WrapField<Map> tagMap;
    private WrapMethod copy;
    @Inject
    private NBTUtils nbtUtils;
    //NBTTagCompound
    private WrapMethod readCompressed;
    private WrapMethod writeCompressed;

    @PostInstance
    private void init() {
        //NBTTagCompound
        tagMap = classNBTTagCompound.getField(FieldFilter.build().type(Map.class));
        copy = classNBTTagCompound.getMethod(MethodFilter.build().name("copy", "clone"));
        //NBTCompressedStreamTools
        readCompressed = classNBTCompressedStreamTools.getMethod(MethodFilter.build().static_(true).returnType(NBTUtils.classNBTTagCompound).parameterType(InputStream.class));
        writeCompressed = classNBTCompressedStreamTools.getMethod(MethodFilter.build().static_(true).returnVoid().parameterType(NBTUtils.classNBTTagCompound.getHandle(), OutputStream.class));
    }

    /**
     * @param file
     * @return NBTTagCompound
     */
    public Object read(File file) {
        try {
            return read(new FileInputStream(file));
        } catch (IOException e) {
            throw Lang.wrapThrow(e, file.getAbsolutePath());
        }
    }

    /**
     * @param is
     * @return NBTTagCompound
     */
    public Object read(InputStream is) {
        try (InputStream in = is) {
            return readCompressed.invoke(null, in);
        } catch (IOException e) {
            throw Lang.wrapThrow(e);
        }
    }

    public void write(Object tag, File file) {
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
            write(tag, new FileOutputStream(file));
        } catch (IOException e) {
            throw Lang.wrapThrow(e, file.getAbsolutePath());
        }
    }

    public void write(Object tag, OutputStream out) {
        try (OutputStream os = out) {
            writeCompressed.invoke(null, tag, os);
        } catch (IOException e) {
            throw Lang.wrapThrow(e);
        }
    }

    public byte[] toBytes(Object tag) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        write(tag, baos);
        return baos.toByteArray();
    }

    /**
     * @param bs
     * @return NBTTagCompound
     */
    public Object fromBytes(byte[] bs) {
        return read(new ByteArrayInputStream(bs));
    }

    public Object copy(Object handle) {
        return copy.invoke(handle);
    }

    public Boolean getBoolean(Object handle, String key) {
        return (Boolean) nbtUtils.getValue(getValue(handle, key));
    }

    public Byte getByte(Object handle, String key) {
        return (Byte) nbtUtils.getValue(getValue(handle, key));
    }

    public byte[] getByteArray(Object handle, String key) {
        return (byte[]) nbtUtils.getValue(getValue(handle, key));
    }

    public Object getCompoundTag(Object handle, String key) {
        return nbtUtils.getValue(getValue(handle, key));
    }

    public Double getDouble(Object handle, String key) {
        return (Double) nbtUtils.getValue(getValue(handle, key));
    }

    public Float getFloat(Object handle, String key) {
        return (Float) nbtUtils.getValue(getValue(handle, key));
    }

    public int[] getIntArray(Object handle, String key) {
        return (int[]) nbtUtils.getValue(getValue(handle, key));
    }

    public Integer getInteger(Object handle, String key) {
        return (Integer) nbtUtils.getValue(getValue(handle, key));
    }

    public Set<String> getKeySet(Object handle) {
        Map tagMap = getTagMap(handle, false);
        return tagMap == null ? new HashSet<>() : tagMap.keySet();
    }

    public Long getLong(Object handle, String key) {
        return (Long) nbtUtils.getValue(getValue(handle, key));
    }

    public Short getShort(Object handle, String key) {
        return (Short) nbtUtils.getValue(getValue(handle, key));
    }

    public String getString(Object handle, String key) {
        return (String) nbtUtils.getValue(getValue(handle, key));
    }

    public Object getTag(Object handle, String key) {
        return getValue(handle, key);
    }

    public Object getTagList(Object handle, String key) {
        return getValue(handle, key);
    }

    public boolean isEmpty(Object handle) {
        Map tagMap = getTagMap(handle, false);
        return tagMap == null || tagMap.isEmpty();
    }

    public Map getTagMap(Object handle, boolean createIfNotExsit) {
        Map map = this.tagMap.get(handle);
        if (map == null && createIfNotExsit) {
            map = new HashMap();
            this.tagMap.set(handle, map);
        }
        return map;
    }

    public void removeTag(Object handle, String key) {
        Map tagMap = getTagMap(handle, false);
        if (tagMap != null) {
            tagMap.remove(key);
        }
    }

    public void setBoolean(Object handle, String key, boolean value) {
        setByte(handle, key, (byte) (value ? 1 : 0));
    }

    public void setByte(Object handle, String key, byte value) {
        Map map = getTagMap(handle, true);
        map.put(key, value);
    }

    public void setByteArray(Object handle, String key, byte[] value) {
        Map map = getTagMap(handle, true);
        map.put(key, nbtUtils.newNBTByteArray(value));
    }

    public void setDouble(Object handle, String key, double value) {
        Map map = getTagMap(handle, true);
        map.put(key, nbtUtils.newNBTDouble(value));
    }

    public void setFloat(Object handle, String key, float value) {
        Map map = getTagMap(handle, true);
        map.put(key, nbtUtils.newNBTFloat(value));
    }

    public void setIntArray(Object handle, String key, int[] value) {
        Map map = getTagMap(handle, true);
        map.put(key, nbtUtils.newNBTIntArray(value));
    }

    public void setInteger(Object handle, String key, int value) {
        Map map = getTagMap(handle, true);
        map.put(key, nbtUtils.newNBTInt(value));
    }

    public void setLong(Object handle, String key, long value) {
        Map map = getTagMap(handle, true);
        map.put(key, nbtUtils.newNBTLong(value));
    }

    public void setShort(Object handle, String key, short value) {
        Map map = getTagMap(handle, true);
        map.put(key, nbtUtils.newNBTShort(value));
    }

    public void setString(Object handle, String key, String value) {
        Map map = getTagMap(handle, true);
        map.put(key, nbtUtils.newNBTString(value));
    }

    public void setTag(Object handle, String key, Object vlaue) {
        getTagMap(handle, true).put(key, vlaue);
    }

    public boolean containsKey(Object nbtTag, String key) {
        Map tagMap = getTagMap(nbtTag, false);
        return tagMap != null && tagMap.containsKey(key);
    }

    public Object getValue(Object handle, String key) {
        Map tagMap = getTagMap(handle, false);
        return tagMap == null ? null : tagMap.get(key);
    }

    public String getNBTFormatJson(Object nbtTag) {
        StringJoiner sj = new StringJoiner(",", "{", "}");
        Map<String, Object> tagMap = this.tagMap.get(nbtTag);
        tagMap.forEach((k, v) -> {
            sj.add(k + ":" + nbtUtils.getNBTFormatJson(v));
        });
        return sj.toString();
    }

    public Object newTagCompound() {
        try {
            return classNBTTagCompound.getHandle().newInstance();
        } catch (Exception e) {
            throw Lang.wrapThrow(e);
        }
    }

    public int size(Object nbtTag) {
        Map map = (Map) nbtUtils.getValue(nbtTag);
        return map.size();
    }

}
